#include <game/ScreenManager.h>
#include "ScreenManagerDebugImpl.h"
#include <game/flow/Manager.h>
#include <game/flow/EntryDefinition.h>
#include <game/ScreenReference.h>
#include <game/Root.h>
#include <game/Screen.h>
#include <game/services/DelayedActions.h>
#include <game/services/ApplicationService.h>
#include <lib3d/hard/Hardware.h>
#include <game/DebugInfoManager.h>

#include <boost/bind.hpp>

ARELLO_GAME_NAMESPACE_DECL_START
	
using namespace services;

ScreenManager::ScreenManager()
	: game(0)
	, flowManager(0)
	, debugImpl(0)
	, currentEntry(0)
{ }

ScreenManager::~ScreenManager()
{ }

void ScreenManager::SetGame(Root* value)
{
	this->game = value;
	tryInitialize();
}

void ScreenManager::SetFlowManager(flow::Manager* value)
{
	this->flowManager = value;
	tryInitialize();
}

bool ScreenManager::LifeCycle(time_float dt)
{
	if (currentScreen.get())
		return currentScreen->LifeCycle(dt);

	return true;
}

void ScreenManager::render(liba::lib3d::hard::Hardware* hardware, DebugInfoManager* value)
{
	if (currentScreen.get())
		return currentScreen->render(hardware, value);
}

void ScreenManager::Shutdown()
{
	this->currentScreen.reset();
}

void ScreenManager::SchedulePush(const std::string& exitId)
{
	if (!this->game || !this->flowManager)
	{
		LIBA_FAIL("Screen manager isn't initialized yet");
		return;
	}
	
	if (exitId == "#Pop")
	{
		this->game->Query<DelayedActions>()->Schedule(boost::bind(&ScreenManager::popScreen, this, ((game::ScreenReference*)0)));
		return;
	}
	
	flow::EntryDefinition* previousEntry = this->currentEntry;
	screensStack.push(ScreenPair(this->currentEntry, static_cast<Screen*>(0)));
	this->currentEntry = this->flowManager->SwitchScreen(this->currentEntry, exitId);
	
	if (!this->currentEntry)
	{
		LIBA_FAIL("Things went wrong at flow manager");
		this->currentEntry = previousEntry;
		screensStack.pop();
		return;
	}
	
	if (this->debugImpl)
		this->debugImpl->InvokeScreenChanged(this->currentEntry->GetName());
	
	ScreenReference* newScreenReference = 
	this->currentEntry->GetScreenReference();
	
	if (!newScreenReference)
	{
		LIBA_FAIL("Flow entry didn't return screen reference");
		return;
	}
	
	this->game->Query<DelayedActions>()->
	Schedule(boost::bind(&ScreenManager::pushScreen, this, newScreenReference));
}

void ScreenManager::ScheduleSwitch(const std::string& exitId, bool useLoadingScreen)
{
	if (!this->game || !this->flowManager)
	{
		LIBA_FAIL("Screen manager isn't initialized yet");
		return;
	}

	if (exitId == "#Pop")
	{
		this->game->Query<DelayedActions>()->Schedule(boost::bind(&ScreenManager::popScreen, this, ((game::ScreenReference*)0)));
		return;
	}
	
	flow::EntryDefinition* previousEntry = this->currentEntry;
	this->currentEntry = this->flowManager->SwitchScreen(this->currentEntry, exitId);

	if (!this->currentEntry)
	{
		LIBA_FAIL("Things went wrong at flow manager");
		this->currentEntry = previousEntry;
		return;
	}
	
	if (this->debugImpl)
		this->debugImpl->InvokeScreenChanged(this->currentEntry->GetName());

	ScreenReference* newScreenReference = 
		this->currentEntry->GetScreenReference();
	
	if (!newScreenReference)
	{
		LIBA_FAIL("Flow entry didn't return screen reference");
		return;
	}

	this->game->Query<DelayedActions>()->
		Schedule(boost::bind(&ScreenManager::switchScreen, this, newScreenReference, useLoadingScreen));
}

void ScreenManager::ScheduleClearAndSwitch(const std::string& exitId, bool useLoadingScreen)
{
	if (!this->game || !this->flowManager)
	{
		LIBA_FAIL("Screen manager isn't initialized yet");
		return;
	}
	
	if (exitId == "#Pop")
	{
		this->game->Query<DelayedActions>()->Schedule(boost::bind(&ScreenManager::popScreen, this, ((game::ScreenReference*)0)));
		return;
	}
	
	flow::EntryDefinition* previousEntry = this->currentEntry;
	this->currentEntry = this->flowManager->SwitchScreen(this->currentEntry, exitId);
	
	if (!this->currentEntry)
	{
		LIBA_FAIL("Things went wrong at flow manager");
		this->currentEntry = previousEntry;
		return;
	}
	
	if (this->debugImpl)
		this->debugImpl->InvokeScreenChanged(this->currentEntry->GetName());
	
	ScreenReference* newScreenReference = 
	this->currentEntry->GetScreenReference();
	
	if (!newScreenReference)
	{
		LIBA_FAIL("Flow entry didn't return screen reference");
		return;
	}
	
	this->game->Query<DelayedActions>()->
		Schedule(boost::bind(&ScreenManager::clearAndSwitchScreen, this, newScreenReference, useLoadingScreen));
}

void ScreenManager::ScheduleRestart()
{
	if (!this->game || !this->flowManager)
	{
		LIBA_FAIL("Screen manager isn't initialized yet");
		return;
	}
	
	if (!this->currentEntry)
	{
		LIBA_FAIL("Things went wrong at flow manager");
		return;
	}
	
	if (this->debugImpl)
		this->debugImpl->InvokeScreenChanged(this->currentEntry->GetName());
	
	ScreenReference* newScreenReference = 
	this->currentEntry->GetScreenReference();
	
	if (!newScreenReference)
	{
		LIBA_FAIL("Flow entry didn't return screen reference");
		return;
	}
	
	this->game->Query<DelayedActions>()->
		Schedule(boost::bind(&ScreenManager::switchScreen, this, newScreenReference, false));
}

void ScreenManager::clearAndSwitchScreen(ScreenReference* reference, bool useLoadingScreen)
{
	if (useLoadingScreen)
	{
		this->currentScreen.reset();

		services::ApplicationService* appService = game->Query<services::ApplicationService>();
		if (appService)
			appService->ForceLoadingScreen();
		else
			LIBA_FAIL_UNEXPECTED();
	}

	while(!screensStack.empty())
	{
		ScreenPair previousScreen = screensStack.top();
		screensStack.pop();
		
		delete previousScreen.second;
	}
	
	this->currentScreen = reference->ConstructScreen(*this->game);
}

void ScreenManager::switchScreen(ScreenReference* reference, bool useLoadingScreen)
{
	if (useLoadingScreen)
	{
		this->currentScreen.reset();

		services::ApplicationService* appService = game->Query<services::ApplicationService>();
		if (appService)
			appService->ForceLoadingScreen();
		else
			LIBA_FAIL_UNEXPECTED();
	}

	this->currentScreen = reference->ConstructScreen(*this->game);
}

void ScreenManager::pushScreen(ScreenReference* reference)
{
	this->currentScreen->Deactivate();
	screensStack.top().second = this->currentScreen.release();
	this->currentScreen       = reference->ConstructScreen(*this->game);
}

void ScreenManager::popScreen(ScreenReference* reference)
{
	ScreenPair previousScreen = screensStack.top();
	screensStack.pop();
	
	this->currentEntry  = previousScreen.first;
	this->currentScreen = std::auto_ptr<Screen>(previousScreen.second);
	this->currentScreen->Activate();
}

void ScreenManager::tryInitialize()
{
	if (!this->game || !this->flowManager)
		return;

	if (config::ENABLE_DEBUG_SERVICES)
	{
		std::auto_ptr<ScreenManagerDebugImpl> localDebugImpl(
			new ScreenManagerDebugImpl(*this));
		this->debugImpl = Register(localDebugImpl, *this->game);
	}

	this->currentEntry = this->flowManager->CreateStartEntry();

	if (!this->currentEntry)
	{
		LIBA_FAIL("Flow manager doesn't have default entry");
		return;
	}

	if (this->debugImpl)
		this->debugImpl->InvokeScreenChanged(this->currentEntry->GetName());

	ScreenReference* newScreenReference = 
		this->currentEntry->GetScreenReference();
	
	switchScreen(newScreenReference, false);
}

ARELLO_GAME_NAMESPACE_DECL_END